#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import idc, idaapi, idautils
import ida_idaapi
idaapi.require("common")

# Function type for each func_id
FUNC_TYPES = [
    "normal",
    "abort",
    "asmcgocall",
    "asyncPreempt",
    "cgocallback",
    "debugCallV2",
    "gcBgMarkWorker",
    "goexit",
    "gogo",
    "gopanic",
    "handleAsyncEvent",
    "mcall",
    "morestack",
    "mstart",
    "panicwrap",
    "rt0_go",
    "runfinq",
    "runtime_main",
    "sigpanic",
    "systemstack",
    "systemstack_switch",
    "wrapper" # any autogenerated code (hash/eq algorithms, method wrappers, etc.)
]

def get_gopclntbl_seg_start_addr():
    seg_start_addr = idc.BADADDR
    # .gopclntab found in (older) PE & ELF binaries, __gopclntab found in macho binaries,
    # runtime.pclntab in .rdata for newer PE binaries
    seg = common.get_seg(['.gopclntab', '__gopclntab'])

    if seg is None:
        seg_start_addr = common.get_seg_start_addr_from_rdata(['runtime.pclntab'])
    else:
        seg_start_addr = seg.start_ea

    return seg_start_addr

class Pclntbl():
    '''
    PcLineTable:
    Refer:
        1. golang.org/s/go12symtab
        2. https://golang.org/src/debug/gosym/pclntab.go
        3. https://golang.org/src/runtime/symtab.go

    For an amd64 system, the pclntab symbol begins:

        [4] 0xfffffffb
        [2] 0x00 0x00
        [1] 0x01
        [1] 0x08
        [8] N (size of function symbol table)
        [8] pc0
        [8] func0 offset
        [8] pc1
        [8] func1 offset
        …
        [8] pcN
        [4] int32 offset from start to source file table
        … and then data referred to by offset, in an unspecified order …

    type pcHeader struct {     // Go version 1.16+
        magic          uint32  // 0xFFFFFFFA
        pad1, pad2     uint8   // 0,0
        minLC          uint8   // min instruction size
        ptrSize        uint8   // size of a ptr in bytes
        nfunc          int     // number of functions in the module
        nfiles         uint    // number of entries in the file tab.
        funcnameOffset uintptr // offset to the funcnametab variable from pcHeader
        cuOffset       uintptr // offset to the cutab variable from pcHeader
        filetabOffset  uintptr // offset to the filetab variable from pcHeader
        pctabOffset    uintptr // offset to the pctab varible from pcHeader
        pclnOffset     uintptr // offset to the pclntab variable from pcHeader
    }

    // pcHeader holds data used by the pclntab lookups.
    type pcHeader struct {     // Go version 1.18+
        magic          uint32  // 0xFFFFFFF0
        pad1, pad2     uint8   // 0,0
        minLC          uint8   // min instruction size
        ptrSize        uint8   // size of a ptr in bytes
        nfunc          int     // number of functions in the module
        nfiles         uint    // number of entries in the file tab
        textStart      uintptr // base for function entry PC offsets in this module, equal to moduledata.text
        funcnameOffset uintptr // offset to the funcnametab variable from pcHeader
        cuOffset       uintptr // offset to the cutab variable from pcHeader
        filetabOffset  uintptr // offset to the filetab variable from pcHeader
        pctabOffset    uintptr // offset to the pctab variable from pcHeader
        pclnOffset     uintptr // offset to the pclntab variable from pcHeader
    }
    '''
    def __init__(self, start_addr, magic_number):
        self.start_addr       = start_addr
        self.goroot           = ""
        self.min_lc           = 0 # "instruction size quantum",                          i.e. minimum length of an instruction code
        self.ptr_sz           = 0 # size in bytes of pointers and the predeclared "int", "uint", and "uintptr" types
        self.funcnametab_addr = idc.BADADDR
        self.func_num         = 0 # Number of functions
        self.func_tbl_addr    = idc.BADADDR
        self.func_tbl_sz      = 0 # Size of whole function table
        self.pctbl_addr       = idc.BADADDR
        self.cutbl_addr       = idc.BADADDR
        self.text_sect_addr   = idc.BADADDR
        #self.func_sym_tbl    = dict() # pc -> FunctionSymbolTableEntry
        self.end_pc           = 0
        self.srcfile_tbl_addr = idc.BADADDR
        self.srcfile_num      = 0 # Number of src files
        self.srcfiles         = list()
        self.magic_number     = magic_number

    def parse_hdr(self):
        '''
        Refer: function [go12Init()] in https://golang.org/src/debug/gosym/pclntab.go
        '''
        if self.magic_number == common.MAGIC_112 \
            or self.magic_number == common.MAGIC_116 \
            or self.magic_number == common.MAGIC_118 \
            or self.magic_number == common.MAGIC_120:
            common._info(f"Magic Number: {self.magic_number:#x}")
        else:
            common._error("Invalid pclntbl header magic number!")
            idc.qexit(1)
            #raise Exception("Invalid pclntbl header magic number!")

        idc.create_data(self.start_addr, idc.FF_DWORD, 4, ida_idaapi.BADADDR)
        idc.set_cmt(self.start_addr, "Magic Number", 0)
        idc.set_name(self.start_addr, "runtime_symtab", flags=idaapi.SN_FORCE)
        idaapi.auto_wait()

        if idc.get_wide_word(self.start_addr + 4) & 0xFFFF != 0:
            raise Exception("Invalid pclntbl header")
        idc.create_data(self.start_addr + 4,idc.FF_WORD, 2, ida_idaapi.BADADDR)

        self.min_lc = idc.get_wide_byte(self.start_addr + 6) & 0xFF
        if (self.min_lc != 1) and (self.min_lc != 2) and (self.min_lc != 4):
            raise Exception("Invalid pclntbl minimum LC!")
        idc.set_cmt(self.start_addr + 6, "instruction size quantum", 0)
        idaapi.auto_wait()

        self.ptr_sz = idc.get_wide_byte(self.start_addr + 7) & 0xFF
        if (self.ptr_sz != 4) and (self.ptr_sz != 8):
            raise Exception("Invalid pclntbl pointer size!")
        idc.set_cmt(self.start_addr + 7, "ptr size", 0)
        idaapi.auto_wait()

        self.func_num = common.read_mem(self.start_addr + 8, forced_addr_sz=self.ptr_sz)
        common._info(f"Total functions number: {self.func_num:#x}\n")
        self.func_tbl_sz = self.func_num * 2 * self.ptr_sz
        idc.set_cmt(self.start_addr + 8, "Functions number", 0)
        idaapi.auto_wait()

        if self.magic_number == common.MAGIC_112:
            funcs_entry = self.start_addr + 8
            self.func_tbl_addr = funcs_entry + self.ptr_sz
            idc.set_name(funcs_entry, "funcs_entry", flags=idaapi.SN_FORCE)
            idc.set_name(self.func_tbl_addr, "pc0", flags=idaapi.SN_FORCE)
            idaapi.auto_wait()
        if self.magic_number == common.MAGIC_116:
            self.srcfile_num = common.read_mem(self.start_addr + 8 + self.ptr_sz, forced_addr_sz=self.ptr_sz)
            idc.set_cmt(self.start_addr + 8 + self.ptr_sz, "srcfile count number", 0)
            common._info(f"Total srcfile number: {self.srcfile_num:#x}")
            idaapi.auto_wait()

            funcname_tbl_off = common.read_mem(self.start_addr + 8 + 2*self.ptr_sz,   forced_addr_sz=self.ptr_sz)
            self.funcnametab_addr = self.start_addr + funcname_tbl_off
            idc.set_cmt(self.start_addr + 8 + 2*self.ptr_sz, f"func names table offset, real addr: {self.funcnametab_addr:#x}", 0)
            idaapi.auto_wait()

            cutab_off = common.read_mem(self.start_addr + 8 + 3*self.ptr_sz, forced_addr_sz=self.ptr_sz)
            self.cutbl_addr = self.start_addr + cutab_off
            idc.set_cmt(self.start_addr + 8 + 3*self.ptr_sz, f"cu table offset, real addr: {self.cutbl_addr:#x}", 0)
            idaapi.auto_wait()

            srcfile_tbl_off = common.read_mem(self.start_addr + 8 + 4*self.ptr_sz, forced_addr_sz=self.ptr_sz)
            self.srcfile_tbl_addr = self.start_addr + srcfile_tbl_off
            idc.set_cmt(self.start_addr + 8 + 4*self.ptr_sz, f"src file table offset, real addr: {self.srcfile_tbl_addr:#x}", 0)
            idc.set_name(self.srcfile_tbl_addr, "runtime_filetab", flags=idaapi.SN_FORCE)
            idaapi.auto_wait()

            pctab_off = common.read_mem(self.start_addr + 8 + 5*self.ptr_sz, forced_addr_sz=self.ptr_sz)
            self.pctbl_addr = self.start_addr + pctab_off
            idc.set_cmt(self.start_addr + 8 + 5*self.ptr_sz, f"pc table offset, real addr: {self.pctbl_addr:#x}", 0)
            idaapi.auto_wait()

            funtbl_off = common.read_mem(self.start_addr + 8 + 6*self.ptr_sz,     forced_addr_sz=self.ptr_sz)
            self.func_tbl_addr = self.start_addr + funtbl_off
            idc.set_cmt(self.start_addr + 8 + 6*self.ptr_sz, f"func table offset, real addr: {self.func_tbl_addr:#x}", 0)
            idc.set_name(self.func_tbl_addr, "pc0", flags=idaapi.SN_FORCE)
            idaapi.auto_wait()
        elif self.magic_number == common.MAGIC_118 or self.magic_number == common.MAGIC_120:
            self.srcfile_num = common.read_mem(self.start_addr + 8 + self.ptr_sz, forced_addr_sz=self.ptr_sz)
            idc.set_cmt(self.start_addr + 8 + self.ptr_sz, "srcfile count number", 0)
            common._info(f"Total srcfile number: {self.srcfile_num:#x}")
            idaapi.auto_wait()

            self.text_sect_addr = common.read_mem(self.start_addr + 8 + 2*self.ptr_sz,   forced_addr_sz=self.ptr_sz)
            idc.set_cmt(self.start_addr + 8 + 2*self.ptr_sz, f"text section start addr, =firstmoduladata.text", 0)
            idaapi.auto_wait()

            funcname_tbl_off = common.read_mem(self.start_addr + 8 + 3*self.ptr_sz,   forced_addr_sz=self.ptr_sz)
            self.funcnametab_addr = self.start_addr + funcname_tbl_off
            idc.set_cmt(self.start_addr + 8 + 3*self.ptr_sz, f"func names table offset, real addr: {self.funcnametab_addr:#x}", 0)
            idaapi.auto_wait()

            cutab_off = common.read_mem(self.start_addr + 8 + 4*self.ptr_sz, forced_addr_sz=self.ptr_sz)
            self.cutbl_addr = self.start_addr + cutab_off
            idc.set_cmt(self.start_addr + 8 + 4*self.ptr_sz, f"cu table offset, real addr: {self.cutbl_addr:#x}", 0)
            idaapi.auto_wait()

            srcfile_tbl_off = common.read_mem(self.start_addr + 8 + 5*self.ptr_sz, forced_addr_sz=self.ptr_sz)
            self.srcfile_tbl_addr = self.start_addr + srcfile_tbl_off
            idc.set_cmt(self.start_addr + 8 + 5*self.ptr_sz, f"src file table offset, real addr: {self.srcfile_tbl_addr:#x}", 0)
            idc.set_name(self.srcfile_tbl_addr, "runtime_filetab", flags=idaapi.SN_FORCE)
            idaapi.auto_wait()

            pctab_off = common.read_mem(self.start_addr + 8 + 6*self.ptr_sz, forced_addr_sz=self.ptr_sz)
            self.pctbl_addr = self.start_addr + pctab_off
            idc.set_cmt(self.start_addr + 8 + 6*self.ptr_sz, f"pc table offset, real addr: {self.pctbl_addr:#x}", 0)
            idaapi.auto_wait()

            funtbl_off = common.read_mem(self.start_addr + 8 + 7*self.ptr_sz,     forced_addr_sz=self.ptr_sz)
            self.func_tbl_addr = self.start_addr + funtbl_off
            idc.set_cmt(self.start_addr + 8 + 7*self.ptr_sz, f"func table offset, real addr: {self.func_tbl_addr:#x}", 0)
            idc.set_name(self.func_tbl_addr, "runtime_functab", flags=idaapi.SN_FORCE)
            idaapi.auto_wait()

    def parse_funcs_112(self):
        '''
        Parse function struct and rename function for Go 1.12<=version<1.16
        '''
        for func_idx in range(self.func_num):
            curr_addr = self.func_tbl_addr + func_idx * 2 * self.ptr_sz

            func_addr = common.read_mem(curr_addr, forced_addr_sz=self.ptr_sz)
            if not idc.get_func_name(func_addr):
                idc.del_items(func_addr, idc.DELIT_EXPAND)
                idaapi.auto_wait()
                idc.create_insn(func_addr)
                idaapi.auto_wait()
                if idc.add_func(func_addr):
                    idaapi.auto_wait()
                    common._info(f"Create function @ {func_addr:#x}")

            name_off = common.read_mem(curr_addr + self.ptr_sz, forced_addr_sz=self.ptr_sz)
            name_addr = self.start_addr + self.ptr_sz + name_off
            func_st_addr = name_addr - self.ptr_sz
            func_st = FuncStruct(func_st_addr, self)
            func_st.parse()

            # Make comment for name offset
            idc.set_cmt(curr_addr + self.ptr_sz, f"Func Struct @ {func_st_addr:#x}", 0)
            idaapi.auto_wait()

    def parse_funcs_116(self):
        '''
        Parse function struct and rename function for Go 1.12<=version<1.18
        '''
        for func_idx in range(self.func_num):
            curr_addr = self.func_tbl_addr + func_idx * 2 * self.ptr_sz

            func_addr = common.read_mem(curr_addr, forced_addr_sz=self.ptr_sz)
            if not idc.get_func_name(func_addr):
                idc.del_items(func_addr, idc.DELIT_EXPAND)
                idaapi.auto_wait()
                idc.create_insn(func_addr)
                idaapi.auto_wait()
                if idc.add_func(func_addr):
                    idaapi.auto_wait()
                    common._info(f"Create function @ {func_addr:#x}")

            func_st_addr = self.func_tbl_addr + common.read_mem(curr_addr + self.ptr_sz, forced_addr_sz=self.ptr_sz)
            func_st = FuncStruct(func_st_addr, self)
            func_st.parse()

            # Make comment for name offset
            idc.set_cmt(curr_addr + self.ptr_sz, f"Func Struct @ {func_st_addr:#x}", 0)
            idaapi.auto_wait()

    def parse_funcs_118(self):
        '''
        Parse function struct and rename function for Go 1.18+

        refer: https://go.dev/src/runtime/symtab.go

        moduledata.pclntbl_addr == moduledata.ftab_addr == pcheader.pclntbl_addr == \
            (pcheader.start_addr + pclntbl_off)
        moduledata.ftab: []functab

        type functab struct {
            entryoff uint32 // relative to runtime.text
            funcoff  uint32 // relative to pclntbl_addr
        }

        pclntbl_addr + funcoff == FuncStruct addr
        '''
        for func_idx in range(self.func_num):
            curr_functab_addr = self.func_tbl_addr + func_idx * 8   # functab size =8

            func_entry_off = common.read_mem(curr_functab_addr, forced_addr_sz=4)
            func_st_off    = common.read_mem(curr_functab_addr + 4, forced_addr_sz=4)

            func_entry_addr  = self.text_sect_addr + func_entry_off
            func_st_addr     = self.func_tbl_addr + func_st_off

            if not idc.get_func_name(func_entry_addr):
                idc.del_items(func_entry_addr, idc.DELIT_EXPAND)
                idaapi.auto_wait()
                idc.create_insn(func_entry_addr)
                idaapi.auto_wait()
                if idc.add_func(func_entry_addr):
                    idaapi.auto_wait()
                    common._info(f"Create function @ {func_entry_addr:#x}")

            func_st = FuncStruct(func_st_addr, self)
            func_st.parse()

            idc.set_cmt(curr_functab_addr, f"Function {func_st.name} @ {func_entry_addr:#x}", 0)
            idaapi.auto_wait()
            idc.set_cmt(curr_functab_addr + 4, f"Func Struct @ {func_st_addr:#x}", 0)
            idaapi.auto_wait()

    def parse_srcfile_112(self):
        '''
        Parse and extract source all file names for Go 1.12+
        '''
        srcfile_tbl_off = common.read_mem(self.func_tbl_addr + self.func_tbl_sz + self.ptr_sz, forced_addr_sz=4)
        self.srcfile_tbl_addr = self.start_addr + srcfile_tbl_off
        idc.set_cmt(self.func_tbl_addr + self.func_tbl_sz + self.ptr_sz, \
            f"Source file table addr: {self.srcfile_tbl_addr:#x}", 0)
        idc.set_name(self.srcfile_tbl_addr, "runtime_filetab", flags=idaapi.SN_FORCE)
        idaapi.auto_wait()

        self.srcfile_num = common.read_mem(self.srcfile_tbl_addr, forced_addr_sz=4) - 1
        common._info("--------------------------------------------------------------------------------------")
        common._info(f"Source File paths(Total number: {self.srcfile_num}, default print results are user-defind files):\n")
        for idx in range(self.srcfile_num):
            srcfile_off = common.read_mem((idx+1) * 4 + self.srcfile_tbl_addr, forced_addr_sz=4)
            srcfile_addr = self.start_addr + srcfile_off
            srcfile_path = idc.get_strlit_contents(srcfile_addr)
            if srcfile_path is None or len(srcfile_path) == 0:
                common._error(f"Failed to parse the [{idx+1}] src file(off: {srcfile_off:#x}, addr: @ {srcfile_addr:#x})")
                continue
            srcfile_path = srcfile_path.decode("UTF-8", errors="ignore")
            if len(self.goroot) > 0 and (srcfile_path.startswith(self.goroot) or "/pkg/" in srcfile_path or\
                 srcfile_path == "<autogenerated>" or "_cgo_" in srcfile_path or "go/src/git" in srcfile_path):
                # ignore golang std libs and 3rd pkgs
                common._debug(srcfile_path)
            else:
                # User defined function
                self.srcfiles.append(srcfile_path)
                common._info(srcfile_path)

            idc.create_strlit(srcfile_addr, srcfile_addr + len(srcfile_path) + 1)
            idaapi.auto_wait()
            idc.set_cmt((idx+1) * 4 + self.srcfile_tbl_addr, "", 0)
            idaapi.add_dref((idx+1) * 4 + self.srcfile_tbl_addr, srcfile_addr, idaapi.dr_O)
            idaapi.auto_wait()
        common._info("--------------------------------------------------------------------------------------")

    def parse_srcfile_latest(self):
        '''
        Parse and extract source all file names for Go 1.16+
        '''
        idc.set_cmt(self.start_addr + 8 + 4*self.ptr_sz, f"Source file table addr: {self.srcfile_tbl_addr:#x}", 0)
        idc.set_name(self.srcfile_tbl_addr, "runtime_filetab", flags=idaapi.SN_FORCE)
        idaapi.auto_wait()

        common._info("--------------------------------------------------------------------------------------")
        common._info(f"Source File paths(Total number: {self.srcfile_num:#x}, default print results are user-defind files):\n")
        curr_srcfilename_addr = self.srcfile_tbl_addr
        for idx in range(self.srcfile_num):
            srcfile_path = idc.get_strlit_contents(curr_srcfilename_addr)
            if srcfile_path is None or len(srcfile_path) == 0:
                common._error(f"Failed to parse the [{idx+1}] src file(addr: @ {curr_srcfilename_addr:#x})")
                continue
            srcfile_path = srcfile_path.decode("UTF-8", errors="ignore")
            if len(self.goroot) > 0 and (srcfile_path.startswith(self.goroot) or "/pkg/" in srcfile_path or\
                 srcfile_path == "<autogenerated>" or "_cgo_" in srcfile_path or "go/src/git" in srcfile_path):
                # ignore golang std libs and 3rd pkgs
                common._debug(srcfile_path)
            else:
                # User defined function
                self.srcfiles.append(srcfile_path)
                common._info(srcfile_path)

            idc.create_strlit(curr_srcfilename_addr, curr_srcfilename_addr + len(srcfile_path) + 1)
            idaapi.auto_wait()
            curr_srcfilename_addr += len(srcfile_path) + 1
            idaapi.auto_wait()
        common._info("--------------------------------------------------------------------------------------")

    def parse(self):
        self.parse_hdr()

        if self.magic_number == common.MAGIC_112:
            self.parse_funcs_112()
        elif self.magic_number == common.MAGIC_116:
            self.parse_funcs_116()
        elif self.magic_number == common.MAGIC_118 or self.magic_number == common.MAGIC_120:
            self.parse_funcs_118()
        idaapi.auto_wait()

        self.goroot = common.get_goroot()
        parse_func_pointer()

        if self.magic_number == common.MAGIC_112:
            self.parse_srcfile_112()
        elif self.magic_number == common.MAGIC_116 or self.magic_number == common.MAGIC_118 or self.magic_number == common.MAGIC_120:
            self.parse_srcfile_latest()


class FuncStruct():
    '''
    Old version(Go 1.12):
    Refer: golang.org/s/go12symtab

    // Layout of in-memory per-function information prepared by linker
    // See https://golang.org/s/go12symtab.
    // Keep in sync with linker (../cmd/link/internal/ld/pcln.go:/pclntab)
    // and with package debug/gosym and with symtab.go in package runtime.
    struct Func
    {
        uintptr      entry;     // start pc
        int32        name;      // name (offset to C string)
        int32        args;      // size of arguments passed to function
        int32        frame;     // size of function frame, including saved caller PC
        int32        pcsp;      // pcsp table (offset to pcvalue table)
        int32        pcfile;    // pcfile table (offset to pcvalue table)
        int32        pcln;      // pcln table (offset to pcvalue table)
        int32        nfuncdata; // number of entries in funcdata list
        int32        npcdata;   // number of entries in pcdata list
    };

    Latest version:
    Refer: https://golang.org/src/runtime/runtime2.go

    type _func struct {     // Go 1.16
        entry   uintptr // start pc
        nameoff int32   // function name

        args        int32  // in/out args size
        deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any.

        pcsp      int32
        pcfile    int32
        pcln      int32
        npcdata   int32
        funcID    funcID  // set for certain special runtime functions
        _         [2]int8 // unused
        nfuncdata uint8   // must be last
    }

    type _func struct {     // Go 1.18
        entryoff uint32 // start pc, as offset from moduledata.text/pcHeader.textStart
        nameoff  int32  // function name

        args        int32  // in/out args size
        deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any.

        pcsp      uint32
        pcfile    uint32
        pcln      uint32
        npcdata   uint32
        cuOffset  uint32 // runtime.cutab offset of this function's CU
        funcID    funcID // set for certain special runtime functions, uint8 alias
        flag      funcFlag
        _         [1]byte // pad
        nfuncdata uint8   // must be last, must end on a uint32-aligned boundary
    }

    type _func struct {     // Go 1.20
        entryOff uint32 // start pc, as offset from moduledata.text/pcHeader.textStart
        nameOff  int32  // function name, as index into moduledata.funcnametab.

        args        int32  // in/out args size
        deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any.

        pcsp      uint32
        pcfile    uint32
        pcln      uint32
        npcdata   uint32
        cuOffset  uint32 // runtime.cutab offset of this function's CU
        startLine int32  // line number of start of function (func keyword/TEXT directive)
        funcID    funcID // set for certain special runtime functions
        flag      funcFlag
        _         [1]byte // pad
        nfuncdata uint8   // must be last, must end on a uint32-aligned boundary

        // The end of the struct is followed immediately by two variable-length
        // arrays that reference the pcdata and funcdata locations for this
        // function.

        // pcdata contains the offset into moduledata.pctab for the start of
        // that index's table. e.g.,
        // &moduledata.pctab[_func.pcdata[_PCDATA_UnsafePoint]] is the start of
        // the unsafe point table.
        //
        // An offset of 0 indicates that there is no table.
        //
        // pcdata [npcdata]uint32

        // funcdata contains the offset past moduledata.gofunc which contains a
        // pointer to that index's funcdata. e.g.,
        // *(moduledata.gofunc +  _func.funcdata[_FUNCDATA_ArgsPointerMaps]) is
        // the argument pointer map.
        //
        // An offset of ^uint32(0) indicates that there is no entry.
        //
        // funcdata [nfuncdata]uint32
    }

    // A FuncID identifies particular functions that need to be treated
    // specially by the runtime.
    // Note that in some situations involving plugins, there may be multiple
    // copies of a particular special runtime function.
    // Note: this list must match the list in cmd/internal/objabi/funcid.go.
    type funcID uint8

    const (
        funcID_normal funcID = iota // not a special function
        funcID_abort
        funcID_asmcgocall
        funcID_asyncPreempt
        funcID_cgocallback
        funcID_debugCallV2
        funcID_gcBgMarkWorker
        funcID_goexit
        funcID_gogo
        funcID_gopanic
        funcID_handleAsyncEvent
        funcID_mcall
        funcID_morestack
        funcID_mstart
        funcID_panicwrap
        funcID_rt0_go
        funcID_runfinq
        funcID_runtime_main
        funcID_sigpanic
        funcID_systemstack
        funcID_systemstack_switch
        funcID_wrapper // any autogenerated code (hash/eq algorithms, method wrappers, etc.)
    )

    // A FuncFlag holds bits about a function.
    // This list must match the list in cmd/internal/objabi/funcid.go.
    type funcFlag uint8

    const (
        // TOPFRAME indicates a function that appears at the top of its stack.
        // The traceback routine stop at such a function and consider that a
        // successful, complete traversal of the stack.
        // Examples of TOPFRAME functions include goexit, which appears
        // at the top of a user goroutine stack, and mstart, which appears
        // at the top of a system goroutine stack.
        funcFlag_TOPFRAME funcFlag = 1 << iota

        // SPWRITE indicates a function that writes an arbitrary value to SP
        // (any write other than adding or subtracting a constant amount).
        // The traceback routines cannot encode such changes into the
        // pcsp tables, so the function traceback cannot safely unwind past
        // SPWRITE functions. Stopping at an SPWRITE function is considered
        // to be an incomplete unwinding of the stack. In certain contexts
        // (in particular garbage collector stack scans) that is a fatal error.
        funcFlag_SPWRITE

        // ASM indicates that a function was implemented in assembly.
        funcFlag_ASM
    )
    '''
    def __init__(self, addr, pclntbl):
        self.pclntbl     = pclntbl
        self.addr        = addr
        self.name        = ""
        self.args        = 0
        self.frame       = 0
        self.pcsp        = 0
        self.pcfile      = 0
        self.pcln        = 0
        self.nfuncdata   = 0
        self.npcdata     = 0
        self.deferreturn = 0
        self.startline   = 0
        self.func_id     = -1
        self.func_flag   = -1
        self.func_type   = "normal" #Default type

    def parse(self, is_test=False):
        if self.pclntbl.magic_number == common.MAGIC_112:
            func_addr = common.read_mem(self.addr, forced_addr_sz=self.pclntbl.ptr_sz, read_only=is_test)

            name_addr = common.read_mem(self.addr + self.pclntbl.ptr_sz, forced_addr_sz=4, read_only=is_test) \
                + self.pclntbl.start_addr
        elif self.pclntbl.magic_number == common.MAGIC_116:
            func_addr = common.read_mem(self.addr, forced_addr_sz=self.pclntbl.ptr_sz, read_only=is_test)

            name_addr = common.read_mem(self.addr + self.pclntbl.ptr_sz, forced_addr_sz=4, read_only=is_test) \
                + self.pclntbl.funcnametab_addr
        elif self.pclntbl.magic_number == common.MAGIC_118 or self.pclntbl.magic_number == common.MAGIC_120:
            func_entry_off = common.read_mem(self.addr, forced_addr_sz=4, read_only=is_test)
            func_addr = self.pclntbl.text_sect_addr + func_entry_off

            func_name_off = common.read_mem(self.addr + 4, forced_addr_sz=4, read_only=is_test)
            name_addr = self.pclntbl.funcnametab_addr + func_name_off

        raw_name_str = idc.get_strlit_contents(name_addr)
        if raw_name_str and len(raw_name_str) > 0:
            self.name = common.clean_function_name(raw_name_str)
        else:
            common._debug(f"Failed to get valid function name for func st @ {self.addr:#x}, target func name addr: {name_addr:#x}")

        if is_test: return

        idc.set_cmt(self.addr, f"Func Entry @ {func_addr:#x}", 0)
        idaapi.auto_wait()
        # make comment for func name offset
        if raw_name_str and len(raw_name_str) > 0:
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz, f"Func name offset(Addr @ {name_addr:#x}), name string: {raw_name_str.decode('UTF-8', errors='ignore')}", 0)
        else:
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz, f"Func name offset(Addr @ {name_addr:#x})", 0)
        idaapi.auto_wait()

        # Make name string
        if len(self.name) > 0:
            if idc.create_strlit(name_addr, name_addr + len(raw_name_str) + 1):
                idaapi.auto_wait()
                common._debug(f"Match func_name @ {name_addr:#x} : {self.name}")
            else:
                common._error(f"Make func_name_str [{self.name}] failed @ {name_addr:#x}")

        # Rename function
        real_func_addr = idaapi.get_func(func_addr)
        if len(self.name) > 0 and real_func_addr is not None:
            if idc.set_name(real_func_addr.start_ea, self.name, flags=idaapi.SN_FORCE):
                idaapi.auto_wait()
                common._debug(f"Rename function {real_func_addr.start_ea:#x}: {self.name}")
            else:
                common._error(f"Failed to rename function @ {real_func_addr.start_ea:#x}")
        else:
            common._error(f"Failed to get real function address: {func_addr:#x}")

        if self.pclntbl.magic_number == common.MAGIC_112:
            self.args      = common.read_mem(self.addr + self.pclntbl.ptr_sz + 4, forced_addr_sz=4, read_only=is_test)
            self.frame     = common.read_mem(self.addr + self.pclntbl.ptr_sz + 2*4, forced_addr_sz=4, read_only=is_test)
            self.pcsp      = common.read_mem(self.addr + self.pclntbl.ptr_sz + 3*4, forced_addr_sz=4, read_only=is_test)
            self.pcfile    = common.read_mem(self.addr + self.pclntbl.ptr_sz + 4*4, forced_addr_sz=4, read_only=is_test)
            self.pcln      = common.read_mem(self.addr + self.pclntbl.ptr_sz + 5*4, forced_addr_sz=4, read_only=is_test)
            self.nfuncdata = common.read_mem(self.addr + self.pclntbl.ptr_sz + 6*4, forced_addr_sz=4, read_only=is_test)
            self.npcdata   = common.read_mem(self.addr + self.pclntbl.ptr_sz + 7*4, forced_addr_sz=4, read_only=is_test)
        elif self.pclntbl.magic_number == common.MAGIC_116:
            self.args        = common.read_mem(self.addr + self.pclntbl.ptr_sz + 4, forced_addr_sz=4, read_only=is_test)
            self.deferreturn = common.read_mem(self.addr + self.pclntbl.ptr_sz + 2*4, forced_addr_sz=4, read_only=is_test)
            self.pcsp        = common.read_mem(self.addr + self.pclntbl.ptr_sz + 3*4, forced_addr_sz=4, read_only=is_test)
            self.pcfile      = common.read_mem(self.addr + self.pclntbl.ptr_sz + 4*4, forced_addr_sz=4, read_only=is_test)
            self.pcln        = common.read_mem(self.addr + self.pclntbl.ptr_sz + 5*4, forced_addr_sz=4, read_only=is_test)
            self.npcdata     = common.read_mem(self.addr + self.pclntbl.ptr_sz + 6*4, forced_addr_sz=4, read_only=is_test)
            self.cuOffset    = common.read_mem(self.addr + self.pclntbl.ptr_sz + 7*4, forced_addr_sz=4, read_only=is_test)
            self.func_id     = idc.get_wide_byte(self.addr + self.pclntbl.ptr_sz + 8*4) & 0xFF
            self.nfuncdata   = idc.get_wide_byte(self.addr + self.pclntbl.ptr_sz + 8*4 + 3) & 0xFF

            try:
                self.func_type = FUNC_TYPES[self.func_id]
            except Exception as e:
                self.func_type = FUNC_TYPES[-1]
                common._debug(f"Invalid funcID. Curr func entry: {real_func_addr.start_ea:#x}, func st addr: {self.addr:#x}, func id: {self.func_id:#x}")

        elif self.pclntbl.magic_number == common.MAGIC_118:
            self.args        = common.read_mem(self.addr + 2*4, forced_addr_sz=4, read_only=is_test)
            self.deferreturn = common.read_mem(self.addr + 3*4, forced_addr_sz=4, read_only=is_test)
            self.pcsp        = common.read_mem(self.addr + 4*4, forced_addr_sz=4, read_only=is_test)
            self.pcfile      = common.read_mem(self.addr + 5*4, forced_addr_sz=4, read_only=is_test)
            self.pcln        = common.read_mem(self.addr + 6*4, forced_addr_sz=4, read_only=is_test)
            self.npcdata     = common.read_mem(self.addr + 7*4, forced_addr_sz=4, read_only=is_test)
            self.cuOffset    = common.read_mem(self.addr + 8*4, forced_addr_sz=4, read_only=is_test)
            self.func_id     = idc.get_wide_byte(self.addr + 9*4) & 0xFF
            self.func_flag   = idc.get_wide_byte(self.addr + 9*4 + 1) & 0xFF
            self.nfuncdata   = idc.get_wide_byte(self.addr + 9*4 + 3) & 0xFF

            try:
                self.func_type = FUNC_TYPES[self.func_id]
            except Exception as e:
                self.func_type = FUNC_TYPES[-1]
                common._debug(f"Invalid funcID. Curr func entry: {real_func_addr.start_ea:#x}, \
                func st addr: {self.addr:#x}, func id: {self.func_id:#x}")
        elif self.pclntbl.magic_number == common.MAGIC_120:
            self.args        = common.read_mem(self.addr + 2*4, forced_addr_sz=4, read_only=is_test)
            self.deferreturn = common.read_mem(self.addr + 3*4, forced_addr_sz=4, read_only=is_test)
            self.pcsp        = common.read_mem(self.addr + 4*4, forced_addr_sz=4, read_only=is_test)
            self.pcfile      = common.read_mem(self.addr + 5*4, forced_addr_sz=4, read_only=is_test)
            self.pcln        = common.read_mem(self.addr + 6*4, forced_addr_sz=4, read_only=is_test)
            self.npcdata     = common.read_mem(self.addr + 7*4, forced_addr_sz=4, read_only=is_test)
            self.cuOffset    = common.read_mem(self.addr + 8*4, forced_addr_sz=4, read_only=is_test)
            self.startline   = common.read_mem(self.addr + 9*4, forced_addr_sz=4, read_only=is_test)
            self.func_id     = idc.get_wide_byte(self.addr + 10*4) & 0xFF
            self.func_flag   = idc.get_wide_byte(self.addr + 10*4 + 1) & 0xFF
            self.nfuncdata   = idc.get_wide_byte(self.addr + 10*4 + 3) & 0xFF

            try:
                self.func_type = FUNC_TYPES[self.func_id]
            except Exception as e:
                self.func_type = FUNC_TYPES[-1]
                common._debug(f"Invalid funcID. Curr func entry: {real_func_addr.start_ea:#x}, \
                func st addr: {self.addr:#x}, func id: {self.func_id:#x}")

        if is_test: return

        if self.pclntbl.magic_number == common.MAGIC_112:
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 4, "args", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 2*4, "frame", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 3*4, "pcsp", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 4*4, "pcfile", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 5*4, "pcln", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 6*4, "nfuncdata", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 7*4, "npcdata", 0)
        elif self.pclntbl.magic_number == common.MAGIC_116:
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 4, "args", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 2*4, "deferreturn", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 3*4, "pcsp", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 4*4, "pcfile", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 5*4, "pcln", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 6*4, "npcdata", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 7*4, "cuOffset", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 8*4, f"func_type: {self.func_type}", 0)
            idc.set_cmt(self.addr + self.pclntbl.ptr_sz + 8*4 + 3, "nfuncdata", 0)
        elif self.pclntbl.magic_number == common.MAGIC_118:
            idc.set_cmt(self.addr + 2*4, "args", 0)
            idc.set_cmt(self.addr + 3*4, "deferreturn", 0)
            idc.set_cmt(self.addr + 4*4, "pcsp", 0)
            idc.set_cmt(self.addr + 5*4, "pcfile", 0)
            idc.set_cmt(self.addr + 6*4, "pcln", 0)
            idc.set_cmt(self.addr + 7*4, "npcdata", 0)
            idc.set_cmt(self.addr + 8*4, "cuOffset", 0)
            idc.set_cmt(self.addr + 9*4, f"func_type: {self.func_type}", 0)
            idc.set_cmt(self.addr + 9*4 + 1, "func_flag", 0)
            idc.set_cmt(self.addr + 9*4 + 3, "nfuncdata", 0)
        elif self.pclntbl.magic_number == common.MAGIC_120:
            idc.set_cmt(self.addr + 2*4, "args", 0)
            idc.set_cmt(self.addr + 3*4, "deferreturn", 0)
            idc.set_cmt(self.addr + 4*4, "pcsp", 0)
            idc.set_cmt(self.addr + 5*4, "pcfile", 0)
            idc.set_cmt(self.addr + 6*4, "pcln", 0)
            idc.set_cmt(self.addr + 7*4, "npcdata", 0)
            idc.set_cmt(self.addr + 8*4, "cuOffset", 0)
            idc.set_cmt(self.addr + 9*4, "startline", 0)
            idc.set_cmt(self.addr + 10*4, f"func_type: {self.func_type}", 0)
            idc.set_cmt(self.addr + 10*4 + 1, "func_flag", 0)
            idc.set_cmt(self.addr + 10*4 + 3, "nfuncdata", 0)
        idaapi.auto_wait()


# Function pointers are often used instead of passing a direct address to the
# function -- this function names them based off what they're currently named
# to ease reading
#
# lea     rax, main_GetExternIP_ptr <-- pointer to actual function
# mov     [rsp+1C0h+var_1B8], rax <-- loaded as arg for next function
# call    runtime_newproc <-- function is used inside a new process

def parse_func_pointer():
    renamed = 0
    possible_seg = ("rodata", ".rodata", "LOAD", ".rdata", ".data")

    for segea in idautils.Segments():
        for addr in idautils.Functions(segea, idc.get_segm_end(segea)):
        #for addr in idautils.Functions(text_seg.start_ea, text_seg.end_ea):
            name = idc.get_func_name(addr)

            # Look at data xrefs to the function - find the pointer that is located in .rodata
            data_ref = idaapi.get_first_dref_to(addr)
            while data_ref != idc.BADADDR:
                if idc.get_segm_name(data_ref) in possible_seg:
                    # Only rename things that are currently listed as an offset; eg. off_9120B0
                    if 'off_' in idc.get_name(data_ref):
                        if idc.set_name(data_ref, f"{name}_ptr", flags=idaapi.SN_FORCE):
                            idaapi.auto_wait()
                            renamed += 1
                        else:
                            common._error(f"Failed to name pointer @ {data_ref:#x} for {name}")

                data_ref = idaapi.get_next_dref_to(addr, data_ref)

    common._info(f"Rename {renamed} function pointers.\n")
